package com.amadeus.spin.devtool.soap.shooter;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;

import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.InputSource;

import com.amadeus.spin.devtool.soap.shooter.config.ConfigurationParameters;
import com.amadeus.spin.devtool.soap.shooter.config.InitializeShooter;
import com.amadeus.spin.devtool.soap.shooter.config.SoapshooterScenario;
import com.amadeus.spin.devtool.soap.shooter.process.ReplaceSchemaVersion;
import com.amadeus.spin.devtool.soap.shooter.process.RetrieveHostTraces;
import com.amadeus.spin.devtool.soap.shooter.process.ValidateGrammar;
import com.amadeus.spin.standard.util.Dom4jUtils;
import com.amadeus.spin.standard.util.EncodingUtils;
import com.amadeus.spin.standard.util.FileUtils;
import com.amadeus.spin.standard.util.SoapShooterUtils;
import com.amadeus.spin.standard.util.XPathHelper;
import com.amadeus.spin.util.soapshooter.ShooterException;

/**
 * SOAP shooter client used for unit texting New Trip Flow scenarios
 */
public class Shooter {

  private static final String JANUS_ID_TAG = "JanusID";

  public static final String OUTPUT_DIR_NAME_KEY = "outputdir4request";

  private static final int SEC = 60;

  private static final int MILLI = 1000;

  private static final String DATE_TSTAMP_FORMAT = "yyyyMMdd_HHmmss";

  private static final String DATE_EXEC_DATE_FORMAT = "yyyy/MM/dd 'at' HH:mm:ss";

  private static Map<String, String> variables = new HashMap<String, String>();

  private static Logger logger = Logger.getLogger(Shooter.class);

  private static com.amadeus.spin.util.soapshooter.Shooter theShooter;

  // array of files to be shot and their corresponding schemas
  private static List<String> arrayInputFile = new ArrayList<String>();

  // RecordReplay request initialization
  private static final String INIT_RECORD_REPLAY_REQUEST = "<?xml version=\"1.0\" " +
       "encoding=\"iso-8859-1\"?><RecordReplay>" +
       "<IDSAV>%context</IDSAV><SITE>%site</SITE><LANGUAGE>%language</LANGUAGE>" + "<FCT>RECORD_REPLAY_INIT</FCT>" +
       "<TRANSACTION_ID>RecordReplay</TRANSACTION_ID></RecordReplay>";

  private SoapshooterScenario conf;

  /**
   * Do not delete this ! It is used by the ghosting project !
   */
  private SoapScenarioListener listener = null;

  // private String m_previousOut; // save one request xml out
  private Stack<String> previousOut = new Stack<String>();

  private Collection<String> outputFiles = new ArrayList<String>();

  public Shooter(boolean doNotAllowManualScenario, boolean doNotSuppressExceptions) {
    ConfigurationParameters.setAllowManual(doNotAllowManualScenario);
  }

  public Shooter() {
  }

  public void setListener(SoapScenarioListener thelistener) {
    listener = thelistener;
  }

  /**
   * Main method of the soapshooter.
   */
  public static void main(String[] args) {
    Shooter shooter = new Shooter();
    CommandLineOptions commandLineOptions = new CommandLineOptions();
    try {
      if (!commandLineOptions.parse(args) || !commandLineOptions.setupEnvironment()) {
        return;
      }
      if (ConfigurationParameters.isVerbose()) {
        logger.info("Command line arguments : [" + StringUtils.join(args, " ") + "]");
      }
      InitializeShooter.initialize(shooter);
      shooter.process();
    }
    catch (FatalException e) {
      logFlowURL();
      logger.info(Constants.TEST_FAILED);
      logger.info(Constants.BIGLINE);
      try {
        shooter.createLogFiles();
        if (!ConfigurationParameters.isVerbose() && e.getCause() != null) {
          logger.error("Errro caught, printing verbose Stack Trace ", e);
        }
      }
      catch (FatalException e1) {
        logger.error("Fatal exception during log creation : " + e.getMessage());
      }
      logger.error("Error while processing scenario :\n" + e.getMessage() + "\n");
    }
    catch (AbortException e) {
      // early exception, something failed during initialization.
      logger.error(e.getMessage());
    }
  }

  /**
   * Calls <code>ProcessInput</code> from an xml input node.
   *
   * @param processInputClassName
   *          the class name containing the xml modifier.
   * @param inputNode
   * @param xmlIn
   *          Xml file as a String.
   * @return xmlIn preprocessed using processInputClassName.
   * @throws FatalException
   */
  private String processInput(String processInputClassName, Node inputNode, String xmlIn) throws FatalException {
    ProcessInput processInput = (ProcessInput)ConfigurationParameters.getFactory().newInstance(processInputClassName);
    return processInput.processInput(previousOut, xmlIn, inputNode);
  }

  /**
   * Calls <code>ProcessOutput</code> from an xml output node.
   */
  private void processOutput(Node outputNode) throws FatalException {
    SoapshooterScenario.addAttributeToNode(outputNode, OUTPUT_DIR_NAME_KEY, ConfigurationParameters.getOutputDir());
    ProcessOutput proc = (ProcessOutput)ConfigurationParameters.getFactory().newInstance(outputNode.getNodeName());
    proc.processOutput(previousOut.lastElement(), outputNode, theShooter);
  }

  /**
   * Process the provided one/many requests TODO : Split Me
   *
   * @throws FatalException
   */
  public void process() throws FatalException {
    long startTime = System.currentTimeMillis();
    logger.info(Constants.BIGLINE);
    logger.info("SoapShooter Version    " + ConfigurationParameters.getSoapShooterVersion());
    logger.info("Target Server          " + ConfigurationParameters.getFullTargetPath());
    logger.info("Scenario file          " + ConfigurationParameters.getScenarioFileName());
    logger.info("Execution date         " + getDateAsString(DATE_EXEC_DATE_FORMAT));
    if (ConfigurationParameters.getParamsFileName() != null) {
      logger.info("Params file path       " + ConfigurationParameters.getParamsFileName());
      logger.info("Scenario file after if/else application");
      logger.info(ConfigurationParameters.getScenarioProcessed());
    }
    logger.info("Input directory        " + ConfigurationParameters.getRelativeInputDir() + " (" +
        ConfigurationParameters.getInputDir() + ")");
    logger.info("Output directory       " + ConfigurationParameters.getRelativeOutputDir() + " (" +
        ConfigurationParameters.getOutputDir() + ")");
    if (!StringUtils.isEmpty(ConfigurationParameters.getComment())) {
      // String operation, only for display purposes - we remove useless spaces
      // before each line
      // to put our own. we keep spacing within the lines as it is in the
      // scenario.
      logger.info("Comment : \n          " +
          ConfigurationParameters.getComment().trim().replaceAll("\n\\t+", "\n          ").replaceAll("\n\\s+",
              "\n          "));
    }
    else {
      logger.warn("You should comment your scenario. Use the <comment> tag.");
    }
    logger.info(Constants.BIGLINE);

    boolean specialConfig = false;
    if (ConfigurationParameters.isRetrieveHostTraces()) {
      logger.info("Hosttraces will be copied");
      logger.info("from " + ConfigurationParameters.getHosttracesPath());
      logger.info("  to " + ConfigurationParameters.getRelativeOutputDir() + "/" +
          RetrieveHostTraces.OUTPUT_TRACES_SUBDIR + " (" + ConfigurationParameters.getOutputDir() + ")");
      specialConfig = true;
    }
    if (specialConfig) {
      logger.info(Constants.BIGLINE);
    }
    // case where input requests are specified via a scenario file
    if (conf != null) {
      // iterate through the various requests
      if (ConfigurationParameters.isForceOverrideInput()) {
        String xmlIn = ConfigurationParameters.getForceOverrideInput();
        xmlIn = preprocessInput(xmlIn, Constants.FORCE_OVERRIDE, null);
        logSentInput(Constants.FORCE_OVERRIDE, xmlIn);
        previousOut.add(shootRequest(Constants.FORCE_OVERRIDE, xmlIn));
      }
      NodeIterator irequests = conf.getNodeIterator("/scenario/request");
      for (Node request = irequests.nextNode(); request != null; request = irequests.nextNode()) {
        String currentFile = ConfigurationParameters.getInputDir() + "/" + XPathHelper.getProperty(request, "@input");
        logger.info("");
        logger.info("Current Input : " + FileUtils.getFileNameFromPath(currentFile));
        String xmlIn = "";
        if (ConfigurationParameters.getParamsFileName() != null) {
          xmlIn = FileUtils.applyScenarioXsltPreProcessingToString(currentFile,
                ConfigurationParameters.getParamsFileName());
          if (xmlIn == null) {
            throw new FatalException("Could not process if/else for file " + currentFile);
          }
        }
        else {
          xmlIn = FileUtils.getXmlContent(currentFile, logger);
        }
        xmlIn = preprocessInput(xmlIn, currentFile, request);
        // Initialize the Record/Replay Context
        initRecordReplayContext(xmlIn);
        // Saves the input as it looks after the preprocessing.
        // (what is actually sent to the server).
        logSentInput(currentFile, xmlIn);
        if (listener != null) {
          listener.handleRequest(xmlIn);
        }
        String xmlOut = shootRequest(currentFile, xmlIn);
        if (listener != null) {
          listener.handleResponse(xmlOut);
        }
        previousOut.add(xmlOut);
        validateOutput(request);
        variables.clear();
      }
    }
    // TODO: check if this part is ever used
    else if (arrayInputFile.size() > 0) {
      processCommandLineInputs();
    }
    // end of "check if this part is ever used"
    logFlowURL();
    logger.info(Constants.TEST_SUCCESSFUL);
    if (listener != null) {
      listener.onSuccess();
    }
    logger.info(Constants.BIGLINE);
    long endTime = System.currentTimeMillis();
    logResponseTimes(startTime, endTime);
    createLogFiles();
  }

  /**
   * Process the case when inputs are given in command line
   *
   * @throws FatalException
   */
  private void processCommandLineInputs() throws FatalException {
    int index = 0;
    for (index = 0; index < arrayInputFile.size(); index++) {
      String currentFile = arrayInputFile.get(index);
      String xmlIn = "";
      try {
        String requestFileEncoding = EncodingUtils.getFileEncoding(currentFile);
        logger.info("Process " + currentFile + " (Found encoding " + requestFileEncoding + ")");
        xmlIn = FileUtils.readFile(currentFile, requestFileEncoding, ConfigurationParameters.getScenarioFileName());
        if ((xmlIn == null) || (xmlIn.equals(""))) {
          logger.warn("Empty request (" + currentFile + ") - no processing for this file");
          break;
        }
      }
      catch (FileNotFoundException e) {
        String errorMsg = "Could not find " + currentFile;
        logger.error(errorMsg);
        throw new FatalException(errorMsg, e);
      }
      catch (UnsupportedEncodingException e) {
        String errorMsg = "Encoding not supported for  " + currentFile;
        logger.error(errorMsg);
        throw new FatalException(errorMsg, e);
      }
      catch (IOException e) {
        String errorMsg = "Error while reading " + currentFile;
        logger.error(errorMsg);
        throw new FatalException(errorMsg, e);
      }
      initRecordReplayContext(xmlIn);
      shootRequest(currentFile, xmlIn);
    }
  }

  /**
   * Logs information to continue the flow : URL with session Id cookie
   */
  private static void logFlowURL() {
    if (theShooter != null && theShooter.getSessionCookie() != null) {
      logger.info("Full URL to continue this flow: " + ConfigurationParameters.getFullTargetPath() + ";" +
          theShooter.getSessionCookie());
    }
    logger.info(Constants.BIGLINE);
  }

  /**
   * Writes the sent xml to the log output if verbose mode is enabled. Not using
   * log4j to ease the copy/paste of the sent xml. Can be used to save the sent
   * input to a given file. Triggered with s_writeInputsSent, hardcoded to
   * false. Feel free to enable it via commandline, or remove it.
   *
   * @param currentFile
   *          Path to the file where to save the sent content.
   * @param xmlIn
   *          Sent content.
   */
  private void logSentInput(String currentFile, String xmlIn) {
    if (ConfigurationParameters.isVerbose()) {
      logger.info(xmlIn);
    }
  }

  /**
   * Used to create log files. Now only creates the zip file if requested.
   *
   * @throws FatalException
   */
  private void createLogFiles() throws FatalException {
    if (ConfigurationParameters.isZip()) {
      StringBuffer pathToScenarioFilewithoutExtension = new StringBuffer();
      String scenarioFileNameOnly = new File(ConfigurationParameters.getScenarioFileName()).getName();
      pathToScenarioFilewithoutExtension.append(new File(ConfigurationParameters.getScenarioFileName()).getParent());
      pathToScenarioFilewithoutExtension.append(File.separatorChar);
      pathToScenarioFilewithoutExtension.append(scenarioFileNameOnly.substring(0, scenarioFileNameOnly.indexOf('.')));
      createZipFile(pathToScenarioFilewithoutExtension);
    }
  }

  private void validateOutput(Node confrequest) throws FatalException {
    boolean firstGlobalOutputRule = true;
    NodeIterator igoutputs = conf.getNodeIterator("/scenario/global/output/*");
    for (Node outputNode = igoutputs.nextNode(); outputNode != null; outputNode = igoutputs.nextNode()) {
      if (firstGlobalOutputRule) {
        logger.info("Global Output Validation...");
        firstGlobalOutputRule = false;
      }
      processOutput(outputNode);
    }
    boolean firstSpecificOutputRule = true;
    NodeIterator ioutputs = XPathHelper.selectNodeIterator(confrequest, "output/*");
    for (Node outputNode = ioutputs.nextNode(); outputNode != null; outputNode = ioutputs.nextNode()) {
      if (firstSpecificOutputRule) {
        logger.info("Specific Output Validation...");
        firstSpecificOutputRule = false;
      }
      processOutput(outputNode);
    }
  }

  /**
   * @param xmlIn
   *          XML content of current input file.
   * @param currentFile
   *          Name of the input file - only used for logging purposes.
   * @return The xml input file with its nodes modified according to the
   *         scenario.
   * @throws FatalException
   */
  private String preprocessInput(String xmlIn, String currentFile, Node confrequest)
      throws FatalException {
    // Before processing, check if we have any fields in the xml
    // input which need replacing by filters.
    xmlIn = ConfigurationParameters.getFilterHandler().applyFilters(xmlIn);

    /**
     * SoapShooter schema validation : replace schema url from soap with target
     * server schema URL
     */
    if (ConfigurationParameters.isUseTargetServerSchema()) {
      xmlIn = processInput("ReplaceSchema", null, xmlIn);
    }

    /**
     * SoapShooter schema version replacement : replace schema version from soap
     * with new target server schema version
     */
    if (ConfigurationParameters.isUseCustomSchemaVersion()) {
      xmlIn = processInput(ReplaceSchemaVersion.class.getSimpleName(), null, xmlIn);
    }

    // global process input (potentially modify input)
    xmlIn = applyGlobalProcesses(xmlIn, false);

    // command line replacement process
    if (ConfigurationParameters.getReplaceXmlString() != null) {
      InputSource is = new InputSource(new StringReader(ConfigurationParameters.getReplaceXmlString()));
      SoapshooterScenario soapshootconf = new SoapshooterScenario(is);
      // we have command line replacement nodes to apply
      NodeIterator iterinput = soapshootconf.getNodeIterator(ScenarioXPath.SLASH_INPUT);
      for (Node inputNode = iterinput.nextNode(); inputNode != null; inputNode = iterinput.nextNode()) {
        xmlIn = processInput("Replace", inputNode, xmlIn);
      }
    }

    // process input (potentially modify input)
    if (confrequest != null) {
      NodeIterator inputs = XPathHelper.selectNodeIterator(confrequest, ScenarioXPath.INPUT);
      boolean firstSpecificInputRule = true;
      for (Node inputNode = inputs.nextNode(); inputNode != null; inputNode = inputs.nextNode()) {
        if (firstSpecificInputRule) {
          logger.info("Applying Specific Input Rules...");
          firstSpecificInputRule = false;
        }
        xmlIn = processInput(inputNode.getNodeName(), inputNode, xmlIn);
      }
    }
    // Once replacements have been made, the format validation is done
    xmlIn = applyGlobalProcesses(xmlIn, true);

    // Adding janusId if needed
    String janusId = ConfigurationParameters.getJanusId();
    if (janusId != null) {
      xmlIn = addJanusIDToXML(xmlIn, janusId);
    }

    return xmlIn;
  }

  /**
   * Adds the janus ID tag to the end of the input file. If the janus Id tag is present then its value will be set.
   *
   * @param xmlIn
   * @param janusId
   * @return
   * @throws FatalException
   */
  protected String addJanusIDToXML(String xmlIn, String janusId) throws FatalException {
    Document docIn = Dom4jUtils.getDocumentFromString(xmlIn);
    Element janusIdElement = (Element)docIn.getElementsByTagName(JANUS_ID_TAG).item(0);
    if (janusIdElement == null) {
      janusIdElement = docIn.createElement(JANUS_ID_TAG);
      janusIdElement.setTextContent(janusId);
      docIn.getLastChild().appendChild(janusIdElement);
    }
    else {
      janusIdElement.setTextContent(janusId);
    }
    return Dom4jUtils.getStringFromDocument(docIn);
  }

  /**
   * Applies the global process tags.
   *
   * @param xmlIn
   * @param iginputs
   * @param validateXsd
   *          , if false, does not apply xsd validation (useful in case some parameters are set as variables and not
   *          replaced yet)
   * @return
   * @throws FatalException
   */
  private String applyGlobalProcesses(String xmlIn, boolean validateXsd) throws FatalException {
    boolean firstGlobalInputRule = true;
    NodeIterator iginputs = conf.getNodeIterator(ScenarioXPath.SCENARIO_GLOBAL_INPUT);
    for (Node inputNode = iginputs.nextNode(); inputNode != null; inputNode = iginputs.nextNode()) {
      if (firstGlobalInputRule) {
        logger.info("Applying Global Input Rules...");
        firstGlobalInputRule = false;
      }
      if ((validateXsd && ValidateGrammar.class.getSimpleName().equals(inputNode.getNodeName())) ||
          (!validateXsd && !ValidateGrammar.class.getSimpleName().equals(inputNode.getNodeName()))) {
        xmlIn = processInput(inputNode.getNodeName(), inputNode, xmlIn);
      }
    }
    return xmlIn;
  }

  /**
   * Creates a zip file containing the generated files and the files present in
   * './out/' folder.
   *
   * @param scenarioFileNameOnly
   *          Path + Scenario name.
   */
  private void createZipFile(StringBuffer pathToScenarioFilewithoutExtension) {
    pathToScenarioFilewithoutExtension.append("_results_");
    pathToScenarioFilewithoutExtension.append(getDateAsString(DATE_TSTAMP_FORMAT));
    pathToScenarioFilewithoutExtension.append(".zip");
    String zipFullName = pathToScenarioFilewithoutExtension.toString();
    List<String> fileList = new ArrayList<String>();
    if (ConfigurationParameters.isDumpSoapTrace()) {
      fileList.add(ConfigurationParameters.getReportFilePath());
    }
    addFolderFiles(fileList, new File(ConfigurationParameters.getOutputDir() + File.separator));
    logger.info("Zip File : " + zipFullName + " (contains logs and results)");
    String zipLogs = SoapShooterUtils.generateZipFileFromFileList(fileList, zipFullName);
    if (zipLogs != null) {
      logger.warn(zipLogs);
    }
  }

  /**
   * returns a date as a string.
   *
   * @return
   */
  public static String getDateAsString(String format) {
    return new SimpleDateFormat(format).format(new Date());
  }

  /** Initialize the RecordReplay context in Replay mode for SOAP request. */
  private void initRecordReplayContext(String xmlIn) throws FatalException {
    StringBuffer ctxRecordReplayXmlIn;
    int posStart;
    int posEnd;
    int posStart2;
    int posEnd2;
    // Check for RecordReplay context init
    if (ConfigurationParameters.getCtxRecordReplayFilename() != null) {
      // Init string buffer
      ctxRecordReplayXmlIn = new StringBuffer(INIT_RECORD_REPLAY_REQUEST);
      // Replace the 'IDSAV' parameter.
      posStart = ctxRecordReplayXmlIn.toString().indexOf("%context");
      posEnd = posStart + 8;
      ctxRecordReplayXmlIn.replace(posStart, posEnd, ConfigurationParameters.getCtxRecordReplayFilename());
      // Replace 'SITE' parameter
      posStart2 = ctxRecordReplayXmlIn.toString().indexOf("%site");
      posEnd2 = posStart2 + 5;
      posStart = xmlIn.indexOf("<SITE>");
      if (posStart != -1) {
        // Get closure tag
        posEnd = xmlIn.indexOf("</SITE>");

        // Replace the 'SITE' parameter.
        ctxRecordReplayXmlIn.replace(posStart2, posEnd2, xmlIn.substring(posStart + 6, posEnd));
      }
      else {
        // Replace the 'SITE' parameter by DEF.
        ctxRecordReplayXmlIn.replace(posStart2, posEnd2, "DEF");
      }

      // Replace 'LANGUAGE' parameter
      posStart2 = ctxRecordReplayXmlIn.toString().indexOf("%language");
      posEnd2 = posStart2 + 9;
      posStart = xmlIn.indexOf("<LANGUAGE>");
      if (posStart != -1) {
        posEnd = xmlIn.indexOf("</LANGUAGE>");

        // Replace the 'LANGUAGE' parameter.
        ctxRecordReplayXmlIn.replace(posStart2, posEnd2, xmlIn.substring(posStart + 10, posEnd));
      }
      else {
        // Replace the 'LANGUAGE' parameter by GB.
        ctxRecordReplayXmlIn.replace(posStart2, posEnd2, "GB");
      }
      // Check request validity
      String rrRequest = ctxRecordReplayXmlIn.toString();
      if ((rrRequest != null) && (rrRequest.indexOf("%context") == -1) && (rrRequest.indexOf("%site") == -1) &&
          (rrRequest.indexOf("%language") == -1)) {
        // Shoot
        shootRequest(null, rrRequest);
        // Shoot just once
        ConfigurationParameters.setCtxRecordReplayFilename(null);
      }
    }
  }

  /**
   * Shoot a soap request.
   */
  private String shootRequest(String inputFile, String xmlIn) throws FatalException {
    String response = null;
    logger.info("Sending request " + FileUtils.getFileNameFromPath(inputFile) + " to " +
        ConfigurationParameters.getServer() + " via Soap...");
    long startTime = System.currentTimeMillis();
    try {
      response = theShooter.sendRequest(xmlIn);
      long endTime = System.currentTimeMillis();
      logResponseTimes(startTime, endTime);
      if ((ConfigurationParameters.getOutputDir() != null) && (inputFile != null) &&
          !Constants.FORCE_OVERRIDE.equals(inputFile) && ConfigurationParameters.isDumpOutput()) {
        // Build the result file path
        saveOutput(response, inputFile);
      }
    }
    catch (ShooterException e) {
      logger.error(generateExceptionMessage(e));
      throw new FatalException(generateExceptionMessage(e), e);
    }
    return response;
  }

  /**
   * Logs the time difference between a start and an end date.
   *
   * @param startTime
   * @param endTime
   * @return
   */
  private void logResponseTimes(long startTime, long endTime) {
    long elapsedTime = endTime - startTime;
    int millis = (int)(elapsedTime % MILLI);
    int seconds = (int)(elapsedTime / MILLI) % SEC;
    int minutes = (int)(elapsedTime / (SEC * MILLI));
    logger.info(String.format("Execution finished in : %s min, %s sec, %s ms.", minutes, seconds, millis));
  }

  /**
   * generates a generic error message
   *
   * @param e
   * @return
   */
  private String generateExceptionMessage(ShooterException e) {
    return "An exception occured during the shooting.\nMost possible reasons :\n" +
        "  - target server is down or does not exist\n" +
        "  - target webapp is not available\n" +
        "  - xsd are not deployed correctly on target server (check in the logs to see where " +
        "the server expects it and copy them)\n" +
        "  - the site you're using does not exist in database ('ParameterContext service for key ([site code], " +
        "[language code]) could not be initialized:')\n" +
        "Check your server logs for further details.\n\nException was: " + e.getMessage();
  }

  /**
   *
   * @param response
   *          Response from the targeted server.
   * @param outputFilePath
   *          Path to the file to save the response.
   */
  private void saveOutput(String response, String inputFile) {
    String outputFilePath = null;
    String inputFileName = FileUtils.getFileNameFromPath(inputFile);

    outputFilePath = ConfigurationParameters.getOutputDir() + "/" + "result_" + inputFileName;

    // If file already exists we suffix it with a number
    if (outputFiles.contains(outputFilePath) && FileUtils.fileExists(outputFilePath)) {
      outputFilePath = getSuffixedFileName(outputFilePath);
    }
    outputFiles.add(outputFilePath);

    logger.info("Response Size: " + response.length() + " chars.");
    String respEncoding = EncodingUtils.getStringEncoding(response);

    try {
      response = Dom4jUtils.getTidyXml(response, respEncoding);
    }
    catch (TransformerFactoryConfigurationError e1) {
      logger.error("Response xml could not be read because of internal configuration.");
    }
    catch (TransformerException e1) {
      logger.error("Response xml could not be properly formatted (might be malformed?).");
    }

    try {
      FileUtils.writeDataToFileWithEncoding(response, outputFilePath, respEncoding);
    }
    catch (IOException e) {
      logger.warn("Unable to save response to " + outputFilePath, e);
    }
  }

  /**
   * Generates a file name that does not exist on the disk
   *
   * @param filePath
   * @return
   */
  private String getSuffixedFileName(String filePath) {
    String suffixedFileName = filePath;
    int idx = filePath.lastIndexOf('.');
    String extension = "";
    if (idx > -1) {
      extension = filePath.substring(idx);
      filePath = filePath.substring(0, idx);
      int i = 1;
      while (outputFiles.contains(getIncrementedFilePath(filePath, extension, i)) &&
          FileUtils.fileExists(getIncrementedFilePath(filePath, extension, i))) {
        i++;
      }
      suffixedFileName = getIncrementedFilePath(filePath, extension, i);
    }
    return suffixedFileName;
  }

  private String getIncrementedFilePath(String filePath, String extension, int i) {
    return filePath + "_" + i + extension;
  }

  /**
   * Scan the currentFolder passed and add all its files and the files contained
   * by its subfolder to file list passed
   *
   * @param fileList
   *          the file list to populate
   *
   * @param currentFolder
   *          the directory to process
   */
  private void addFolderFiles(List<String> fileList, File currentFolder) {
    String[] subFiles = currentFolder.list();

    for (int i = 0; i < subFiles.length; i++) {
      String filePath = subFiles[i];
      File currentSubFile = new File(currentFolder.getPath() + File.separator + filePath);
      if (!currentSubFile.isDirectory()) {
        fileList.add(currentSubFile.getPath());
      }
      else {
        addFolderFiles(fileList, currentSubFile);
      }
    }
  }

  public SoapshooterScenario getConf() {
    return conf;
  }

  public void setConf(SoapshooterScenario theconf) {
    this.conf = theconf;
  }

  public com.amadeus.spin.util.soapshooter.Shooter getShooter() {
    return theShooter;
  }

  public static void setShooter(com.amadeus.spin.util.soapshooter.Shooter shooter) {
    theShooter = shooter;
  }

  public static Map<String, String> getVariables() {
    return variables;
  }

  /**
   * Replaces all the variables of the type $myVariable by its assigned value and all the ^^Filter^ but their values.
   *
   * @param nodeContent
   */
  public static String replaceVariables(String nodeContent) {
    if (StringUtils.contains(nodeContent, "$")) {
      // Apply the variables
      nodeContent = replaceAllVariables(nodeContent);
    }
    if (StringUtils.contains(nodeContent, "^^")) {
      // Apply the filters
      nodeContent = ConfigurationParameters.getFilterHandler().applyFilters(nodeContent);
    }
    return nodeContent;
  }

  /**
   * Applies all the variables collected with the ReplaceVariable tag
   *
   * @param nodeContent
   * @return
   */
  protected static String replaceAllVariables(String nodeContent) {
    Iterator<Entry<String, String>> it = variables.entrySet().iterator();
    while (it.hasNext()) {
      Entry<String, String> entry = it.next();
      String key = entry.getKey();
      if (StringUtils.contains(nodeContent, key)) {
        logger.info("Replacing " + key + " by " + entry.getValue());
        nodeContent = nodeContent.replaceAll("\\" + key, entry.getValue());
      }
    }
    return nodeContent;
  }
}
